/*
 * Copyright 2023 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/image/SkImage_Base.h"

#include "include/core/SkBitmap.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkSize.h"
#include "include/core/SkSurfaceProps.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkDebug.h"
#include "src/core/SkBitmapCache.h"
#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkImageFilterCache.h"
#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkSpecialImage.h"
// #include "src/image/SkRescaleAndReadPixels.h"

#include "oh/OHDrawingAPI.h"

#include <multimedia/image_framework/image_mdk_common.h>
#include <multimedia/image_framework/image/image_packer_native.h>

#include <atomic>
#include <utility>
#include <set>

class SkImageFilter;

static std::set<std::string> g_encodeSupportedFormats;
constexpr size_t OUT_DATA_LENGTH = 25 * 1024 * 1024; // 25MB

SkImage_Base::SkImage_Base(const SkImageInfo& info, uint32_t uniqueID)
        : SkImage(info, uniqueID), fAddedToRasterCache(false) {}

SkImage_Base::~SkImage_Base() {
    return;
//     if (fAddedToRasterCache.load()) {
//         SkNotifyBitmapGenIDIsStale(this->uniqueID());
//     }
}

void SkImage_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
                                               SkIRect origSrcRect,
                                               RescaleGamma rescaleGamma,
                                               RescaleMode rescaleMode,
                                               ReadPixelsCallback callback,
                                               ReadPixelsContext context) const {
    RENDER_UNIMPLEMENTED;
    return;
//     SkBitmap src;
//     SkPixmap peek;
//     SkIRect srcRect;
//     if (this->peekPixels(&peek)) {
//         src.installPixels(peek);
//         srcRect = origSrcRect;
//     } else {
//         // Context TODO: Elevate GrDirectContext requirement to public API.
//         auto dContext = as_IB(this)->directContext();
//         src.setInfo(this->imageInfo().makeDimensions(origSrcRect.size()));
//         src.allocPixels();
//         if (!this->readPixels(dContext, src.pixmap(), origSrcRect.x(), origSrcRect.y())) {
//             callback(context, nullptr);
//             return;
//         }
//         srcRect = SkIRect::MakeSize(src.dimensions());
//     }
//     return SkRescaleAndReadPixels(src, info, srcRect, rescaleGamma, rescaleMode, callback, context);
}

bool SkImage_Base::onAsLegacyBitmap(GrDirectContext* dContext, SkBitmap* bitmap) const {
    RENDER_UNIMPLEMENTED;
    return false;
//     // As the base-class, all we can do is make a copy (regardless of mode).
//     // Subclasses that want to be more optimal should override.
//     SkImageInfo info = fInfo.makeColorType(kN32_SkColorType).makeColorSpace(nullptr);
//     if (!bitmap->tryAllocPixels(info)) {
//         return false;
//     }
//
//     if (!this->readPixels(
//                 dContext, bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) {
//         bitmap->reset();
//         return false;
//     }
//
//     bitmap->setImmutable();
//     return true;
}

sk_sp<SkImage> SkImage_Base::makeSubset(GrDirectContext* direct, const SkIRect& subset) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     if (subset.isEmpty()) {
//         return nullptr;
//     }
//
//     const SkIRect bounds = SkIRect::MakeWH(this->width(), this->height());
//     if (!bounds.contains(subset)) {
//         return nullptr;
//     }
//
//     // optimization : return self if the subset == our bounds
//     if (bounds == subset) {
//         return sk_ref_sp(const_cast<SkImage_Base*>(this));
//     }
//
//     return this->onMakeSubset(direct, subset);
}

sk_sp<SkImage> SkImage_Base::makeSubset(skgpu::graphite::Recorder* recorder,
                                        const SkIRect& subset,
                                        RequiredProperties requiredProps) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     if (subset.isEmpty()) {
//         return nullptr;
//     }
//
//     const SkIRect bounds = SkIRect::MakeWH(this->width(), this->height());
//     if (!bounds.contains(subset)) {
//         return nullptr;
//     }
//
//     return this->onMakeSubset(recorder, subset, requiredProps);
}

sk_sp<SkImage> SkImage_Base::makeWithFilter(GrRecordingContext*,
                                            const SkImageFilter* filter,
                                            const SkIRect& subset,
                                            const SkIRect& clipBounds,
                                            SkIRect* outSubset,
                                            SkIPoint* offset) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     if (!filter || !outSubset || !offset || !this->bounds().contains(subset)) {
//         return nullptr;
//     }
//
//     auto srcSpecialImage = SkSpecialImage::MakeFromImage(
//             nullptr, subset, sk_ref_sp(const_cast<SkImage_Base*>(this)), SkSurfaceProps());
//     if (!srcSpecialImage) {
//         return nullptr;
//     }
//
//     sk_sp<SkImageFilterCache> cache(
//             SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize));
//
//     // The filters operate in the local space of the src image, where (0,0) corresponds to the
//     // subset's top left corner. But the clip bounds and any crop rects on the filters are in the
//     // original coordinate system, so configure the CTM to correct crop rects and explicitly adjust
//     // the clip bounds (since it is assumed to already be in image space).
//     // TODO: Once all image filters support it, we can just use the subset's top left corner as
//     // the source FilterResult's origin.
//     skif::ContextInfo ctxInfo = {
//             skif::Mapping(SkMatrix::Translate(-subset.x(), -subset.y())),
//             skif::LayerSpace<SkIRect>(clipBounds.makeOffset(-subset.topLeft())),
//             skif::FilterResult(srcSpecialImage),
//             fInfo.colorType(),
//             fInfo.colorSpace(),
//             /*fSurfaceProps=*/{},
//             cache.get()};
//     skif::Context context = skif::Context::MakeRaster(ctxInfo);
//
//     return this->filterSpecialImage(
//             context, as_IFB(filter), srcSpecialImage.get(), subset, clipBounds, outSubset, offset);
}

sk_sp<SkImage> SkImage_Base::filterSpecialImage(skif::Context context,
                                                const SkImageFilter_Base* filter,
                                                const SkSpecialImage* specialImage,
                                                const SkIRect& subset,
                                                const SkIRect& clipBounds,
                                                SkIRect* outSubset,
                                                SkIPoint* offset) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     sk_sp<SkSpecialImage> result = filter->filterImage(context).imageAndOffset(context, offset);
//     if (!result) {
//         return nullptr;
//     }
//
//     // The output image and offset are relative to the subset rectangle, so the offset needs to
//     // be shifted to put it in the correct spot with respect to the original coordinate system
//     offset->fX += subset.x();
//     offset->fY += subset.y();
//
//     // Final clip against the exact clipBounds (the clip provided in the context gets adjusted
//     // to account for pixel-moving filters so doesn't always exactly match when finished). The
//     // clipBounds are translated into the clippedDstRect coordinate space, including the
//     // result->subset() ensures that the result's image pixel origin does not affect results.
//     SkIRect dstRect = result->subset();
//     SkIRect clippedDstRect = dstRect;
//     if (!clippedDstRect.intersect(clipBounds.makeOffset(result->subset().topLeft() - *offset))) {
//         return nullptr;
//     }
//
//     // Adjust the geometric offset if the top-left corner moved as well
//     offset->fX += (clippedDstRect.x() - dstRect.x());
//     offset->fY += (clippedDstRect.y() - dstRect.y());
//     *outSubset = clippedDstRect;
//     return result->asImage();
}

void SkImage_Base::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace,
                                                     sk_sp<SkColorSpace> dstColorSpace,
                                                     SkIRect srcRect,
                                                     SkISize dstSize,
                                                     RescaleGamma,
                                                     RescaleMode,
                                                     ReadPixelsCallback callback,
                                                     ReadPixelsContext context) const {
    RENDER_UNIMPLEMENTED;
    return;
//     // TODO: Call non-YUV asyncRescaleAndReadPixels and then make our callback convert to YUV and
//     // call client's callback.
//     callback(context, nullptr);
}

sk_sp<SkImage> SkImage_Base::makeColorSpace(GrDirectContext* direct,
                                            sk_sp<SkColorSpace> target) const {
    return this->makeColorTypeAndColorSpace(direct, this->colorType(), std::move(target));
}

sk_sp<SkImage> SkImage_Base::makeColorSpace(skgpu::graphite::Recorder* recorder,
                                            sk_sp<SkColorSpace> target,
                                            RequiredProperties props) const {
    return this->makeColorTypeAndColorSpace(recorder, this->colorType(), std::move(target), props);
}

sk_sp<SkImage> SkImage_Base::makeColorTypeAndColorSpace(GrDirectContext* dContext,
                                                        SkColorType targetColorType,
                                                        sk_sp<SkColorSpace> targetCS) const {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     if (kUnknown_SkColorType == targetColorType || !targetCS) {
//         return nullptr;
//     }
//
//     SkColorType colorType = this->colorType();
//     SkColorSpace* colorSpace = this->colorSpace();
//     if (!colorSpace) {
//         colorSpace = sk_srgb_singleton();
//     }
//     if (colorType == targetColorType &&
//         (SkColorSpace::Equals(colorSpace, targetCS.get()) || this->isAlphaOnly())) {
//         return sk_ref_sp(const_cast<SkImage_Base*>(this));
//     }
//
//     return this->onMakeColorTypeAndColorSpace(targetColorType, std::move(targetCS), dContext);
}

sk_sp<SkImage> SkImage_Base::makeColorTypeAndColorSpace(skgpu::graphite::Recorder*,
                                                        SkColorType ct,
                                                        sk_sp<SkColorSpace> cs,
                                                        RequiredProperties) const {
    // Default to the ganesh version which should be backend agnostic if this
    // image is, for example, a raster backed image. The graphite subclass overrides
    // this method and things work correctly.
    return this->makeColorTypeAndColorSpace(nullptr, ct, std::move(cs));
}

// https://www.iana.org/assignments/media-types/media-types.xhtml#image
static std::string SkEncodedImageFormatToMime(SkEncodedImageFormat format) {
    switch (format) {
        case SkEncodedImageFormat::kBMP:        return "image/bmp";
        case SkEncodedImageFormat::kGIF:        return "image/gif";
        case SkEncodedImageFormat::kICO:        return "image/x-icon";
        case SkEncodedImageFormat::kJPEG:       return "image/jpeg";
        case SkEncodedImageFormat::kPNG:        return "image/png";
        case SkEncodedImageFormat::kWBMP:       return "image/vnd.wap.wbmp";
        case SkEncodedImageFormat::kWEBP:       return "image/webp";
        case SkEncodedImageFormat::kPKM:        return "image/pkm";
        case SkEncodedImageFormat::kKTX:        return "image/ktx";
        case SkEncodedImageFormat::kASTC:       return "image/astc";
        case SkEncodedImageFormat::kDNG:        return "image/x-adobe-dng";
        case SkEncodedImageFormat::kHEIF:       return "image/heif";
        case SkEncodedImageFormat::kAVIF:       return "image/avif";
        case SkEncodedImageFormat::kJPEGXL:     return "image/jxl";
#ifdef SK_BUILD_FOR_GOOGLE3
        case SkEncodedImageFormat::kUnknown:    return "";
#endif
        default:                                return "";
    }
}

inline Image_MimeType GetMimeType(const char *format) {
    return {const_cast<char *>(format), strlen(format)};
}
sk_sp<SkData> SkImage_Base::encodeToData(SkEncodedImageFormat encodedImageFormat, int quality) const {
    Image_ErrorCode errCode = IMAGE_SUCCESS;
    if (g_encodeSupportedFormats.size() == 0) {
        Image_MimeType* mimeType = nullptr;
        size_t length = 0;
        errCode = OHDrawingAPI::OH_ImagePackerNative_GetSupportedFormats(&mimeType, &length);
        if (errCode != IMAGE_SUCCESS) {
            g_encodeSupportedFormats = {"image/jpeg", "image/webp", "image/png"};
        } else {
            for (size_t count = 0; count < length; count++) {
                if (mimeType[count].data != nullptr) {
                    g_encodeSupportedFormats.insert(std::string(mimeType[count].data));
                }
            }
        }
    }
    std::string encodedStrOHOS = SkEncodedImageFormatToMime(encodedImageFormat);

    // unsupported SkEncodedImageFormat
    if (encodedStrOHOS.empty() || g_encodeSupportedFormats.find(encodedStrOHOS) == g_encodeSupportedFormats.end()) {
        SkDebugf("Not support encoding for %s\n", encodedStrOHOS.c_str());
        return nullptr;
    }
    
    OH_PackingOptions *option = nullptr;
    OH_PackingOptions_Create(&option);
    Image_MimeType mimeType = GetMimeType(encodedStrOHOS.c_str());
    OH_PackingOptions_SetMimeType(option, &mimeType);
    OH_PackingOptions_SetQuality(option, (uint32_t)quality);

    OH_ImagePackerNative *testPacker = nullptr;
    errCode = OH_ImagePackerNative_Create(&testPacker);
    if (errCode != IMAGE_SUCCESS) {
        SkDebugf("OH_ImagePackerNative_Create failed, errCode=%d\n", errCode);
        return nullptr;
    }
    std::unique_ptr<uint8_t[]> outData = std::make_unique<uint8_t[]>(OUT_DATA_LENGTH);
    size_t outDataSize = 0;
    sk_sp<SkData> skData;
    if (auto imageSource = acquireImageSource(); imageSource) {
        errCode = OH_ImagePackerNative_PackToDataFromImageSource(
            testPacker, option, imageSource, outData.get(), &outDataSize);
    } else if (auto pixelmap = acquirePixelMap(); pixelmap) {
        errCode = OH_ImagePackerNative_PackToDataFromPixelmap(
            testPacker, option, pixelmap, outData.get(), &outDataSize);
    } else {
        OH_ImagePackerNative_Release(testPacker);
        SkDebugf("encodeToData failed: no image source or pixel ref.\n");
        return nullptr;
    }
    if (errCode == IMAGE_SUCCESS && outDataSize > 0) {
        skData = SkData::MakeWithCopy(outData.get(), outDataSize);
    } else {
        switch (errCode) {
            case IMAGE_BAD_PARAMETER:
                SkDebugf("encodeToData failed: IMAGE_BAD_PARAMETER\n");
                break;
            case IMAGE_TOO_LARGE:
                SkDebugf("encodeToData failed: IMAGE_TOO_LARGE (bufferSize=%zu)\n", OUT_DATA_LENGTH);
                break;
            default:
                SkDebugf("encodeToData failed: errCode=%d\n", errCode);
                break;
        }
    }
    OH_ImagePackerNative_Release(testPacker);
    
    return skData;
}